package provider

import (
	"context"
	"errors"
	"fmt"
	"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
	"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
	"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
	"github.com/openshift-pipelines/pipelines-as-code/pkg/templates"
	"go.uber.org/zap"
	"gopkg.in/yaml.v3"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"net/url"
	"regexp"
	"strings"
)

var (
	testRetestAllRegex    = regexp.MustCompile(`(?m)^(/retest|/test)\s*$`)
	testRetestSingleRegex = regexp.MustCompile(`(?m)^(/test|/retest)[ \t]+\S+`)
	oktotestRegex         = regexp.MustCompile(`(?m)^/ok-to-test\s*$`)
	cancelAllRegex        = regexp.MustCompile(`(?m)^(/cancel)\s*$`)
	cancelSingleRegex     = regexp.MustCompile(`(?m)^(/cancel)[ \t]+\S+`)
)

const (
	testComment   = "/test"
	retestComment = "/retest"
	cancelComment = "/cancel"
)

const (
	GitHubApp = "GitHubApp"
)

type langOpts struct {
	DetectionFile string
}

// PublicNamespace 平台存放公用信息的namespace
const PublicNamespace = "kubedo"

// SystemNamespace 平台管理组件的namespace
const SystemNamespace = "kubedo-system"

// KdoConfig 镜像仓库配置信息的配置映射文件名
const KdoConfig = "console"

// PipelineCommitMsg  pipeline提交代码的信息，这个信息不会触发流水线运行
const PipelineCommitMsg = "Added or updated files for tekton pipeline."

// DevfilePath devfile.yaml文件路径
const DevfilePath = "devfile.yaml"

// DockerfilePath Dockerfile文件默认路径
const DockerfilePath = "docker/Dockerfile"

const HarborNamespace = "harbor"
const HarborConfig = "harbor-core"

// Config is the top-level console server cli configuration.
type Config struct {
	APIVersion string `yaml:"apiVersion"`
	Kind       string `yaml:"kind"`
	Registry   `yaml:"registry"`
}

// Registry holds configuration for authenticating with OpenShift. The auth method is assumed to be "openshift".
type Registry struct {
	RegistryURL      string `yaml:"registryURL"`
	RegistryUser     string `yaml:"registryUser"`
	RegistryPassword string `yaml:"registryPassword"`
}

// LanguageDetection I hate this part of the code so much.. but we are waiting for UBI images
// having >1.6 golang for integrated templates.
var LanguageDetection = map[string]langOpts{
	"go": {
		DetectionFile: "go.mod",
	},
	"python": {
		DetectionFile: "setup.py",
	},
	"nodejs": {
		DetectionFile: "package.json",
	},
	"java": {
		DetectionFile: "pom.xml",
	},
	"generic": {},
}

func Valid(value string, validValues []string) bool {
	for _, v := range validValues {
		if v == value {
			return true
		}
	}
	return false
}

func IsTestRetestComment(comment string) bool {
	return testRetestSingleRegex.MatchString(comment) || testRetestAllRegex.MatchString(comment)
}

func IsOkToTestComment(comment string) bool {
	return oktotestRegex.MatchString(comment)
}

func IsCancelComment(comment string) bool {
	return cancelAllRegex.MatchString(comment) || cancelSingleRegex.MatchString(comment)
}

func GetPipelineRunFromTestComment(comment string) string {
	if strings.Contains(comment, testComment) {
		return getNameFromComment(testComment, comment)
	}
	return getNameFromComment(retestComment, comment)
}

func GetPipelineRunFromCancelComment(comment string) string {
	return getNameFromComment(cancelComment, comment)
}

func getNameFromComment(typeOfComment, comment string) string {
	splitTest := strings.Split(comment, typeOfComment)
	// now get the first line
	getFirstLine := strings.Split(splitTest[1], "\n")
	// trim spaces
	return strings.TrimSpace(getFirstLine[0])
}

func GetPipelineRunAndBranchNameFromTestComment(comment string) (string, string, error) {
	if strings.Contains(comment, testComment) {
		return getPipelineRunAndBranchNameFromComment(testComment, comment)
	}
	return getPipelineRunAndBranchNameFromComment(retestComment, comment)
}

func GetPipelineRunAndBranchNameFromCancelComment(comment string) (string, string, error) {
	return getPipelineRunAndBranchNameFromComment(cancelComment, comment)
}

// getPipelineRunAndBranchNameFromComment function will take GitOps comment and split the comment
// by /test, /retest or /cancel to return branch name and pipelinerun name.
func getPipelineRunAndBranchNameFromComment(typeOfComment, comment string) (string, string, error) {
	var prName, branchName string
	splitTest := strings.Split(comment, typeOfComment)

	// after the split get the second part of the typeOfComment (/test, /retest or /cancel)
	// as second part can be branch name or pipelinerun name and branch name
	// ex: /test branch:nightly, /test prname branch:nightly
	if splitTest[1] != "" && strings.Contains(splitTest[1], ":") {
		branchData := strings.Split(splitTest[1], ":")

		// make sure no other word is supported other than branch word
		if !strings.Contains(branchData[0], "branch") {
			return prName, branchName, fmt.Errorf("the GitOps comment%s does not contain a branch word", branchData[0])
		}
		branchName = strings.Split(strings.TrimSpace(branchData[1]), " ")[0]

		// if data after the split contains prname then fetch that
		prData := strings.Split(strings.TrimSpace(branchData[0]), " ")
		if len(prData) > 1 {
			prName = strings.TrimSpace(prData[0])
		}
	} else {
		// get the second part of the typeOfComment (/test, /retest or /cancel)
		// as second part contains pipelinerun name
		// ex: /test prname
		getFirstLine := strings.Split(splitTest[1], "\n")
		// trim spaces
		prName = strings.TrimSpace(getFirstLine[0])
	}
	return prName, branchName, nil
}

// CompareHostOfURLS compares the host of two parsed URLs and returns true if
// they are.
func CompareHostOfURLS(uri1, uri2 string) bool {
	u1, err := url.Parse(uri1)
	if err != nil || u1.Host == "" {
		return false
	}
	u2, err := url.Parse(uri2)
	if err != nil || u2.Host == "" {
		return false
	}
	return u1.Host == u2.Host
}

// SetupPipelineFiles 设置pac对应文件
func SetupPipelineFiles(provider Interface, logger *zap.SugaredLogger, event *info.Event, run *params.Run, repo *v1alpha1.Repository) error {
	ctx := context.Background()

	varsMap := make(map[string]string)
	varsMap["repo_name"] = repo.Name
	varsMap["git_provider"] = repo.Spec.GitProvider.Type
	varsMap["port_number"] = "8080"
	if repo.Spec.DevInfo.Port != "" {
		varsMap["port_number"] = repo.Spec.DevInfo.Port
	}
	varsMap["port_name"] = "http"
	varsMap["port_protocol"] = "TCP"
	//s := strings.Split(event.HeadBranch, "/")
	//eventBranch := s[len(s)-1]

	consoleConfig, err := run.Clients.Kube.CoreV1().ConfigMaps(SystemNamespace).Get(ctx, KdoConfig, metav1.GetOptions{})
	if err != nil {
		logger.Errorf("failed to get configmap %s in namespace: %s err:%v", KdoConfig, SystemNamespace, err)
		return err
	}

	var cc Config
	if err = yaml.Unmarshal([]byte(consoleConfig.Data["config.yaml"]), &cc); err != nil {
		panic(err)
	}

	registryURL := ""

	if cc.RegistryURL == "" {
		logger.Warnf("failed to get harbor config from configmap %s in namespace %s. try to get from cluster default harbor.", KdoConfig, SystemNamespace)
		harborConfig, err := run.Clients.Kube.CoreV1().ConfigMaps(HarborNamespace).Get(ctx, HarborConfig, metav1.GetOptions{})
		if err != nil {
			logger.Errorf("failed to get configmap %s in namespace: %s. err:%v", HarborConfig, HarborNamespace, err)
			return err
		}
		registryURL = harborConfig.Data["EXT_ENDPOINT"] + "/api"
	} else {
		registryURL = cc.RegistryURL
	}

	registryEndpoint, _ := url.Parse(registryURL)
	registryHost := registryEndpoint.Host

	varsMap["registry_host"] = registryHost

	templateCM, err := run.Clients.Kube.CoreV1().ConfigMaps(PublicNamespace).Get(ctx, repo.Spec.DevInfo.DevTemplate, metav1.GetOptions{})
	if err != nil {
		logger.Errorf("failed to get configmap %s in namespace %s. err=>%v", repo.Spec.DevInfo.DevTemplate, PublicNamespace, err)
		return err
	}

	// 在默认分支设置devfile.yaml
	exist, _ := provider.CheckFileExist(ctx, event, "", DevfilePath)
	if !exist {
		devfile, ok := templateCM.Data["devfile.yaml"]
		if !ok {
			return errors.New(fmt.Sprintf("failed to get devfile.yaml in configmap %s.", templateCM.Name))
		}
		// 创建新文件
		err = provider.CreateFile(ctx, event, "", DevfilePath, templates.ReplaceFileVariables(devfile, varsMap))
		if err != nil {
			return err
		}
	}

	for _, branch := range repo.Spec.BranchEnvs {
		if branch.BranchName == "" || branch.PrName == "" || branch.AppEnv == "" || branch.PrTemplate == "" || branch.Cluster == "" || len(branch.OnEvent) < 1 {
			logger.Warnf("branch %s missing field, ignore.", branch.BranchName)
			continue
		}
		branchNS := repo.Namespace + "-" + branch.AppEnv

		//处理分支名字，有些符号k8s不支持，需要进行转换
		sanitizedBranchName := strings.ReplaceAll(branch.PrName, ".yaml", "")
		sanitizedBranchName = strings.ReplaceAll(sanitizedBranchName, ".tekton/"+repo.Name+"-"+branch.AppEnv+"-", "")

		varsMap["branch_namespace"] = branchNS
		//原始的分支名
		varsMap["branch_name"] = branch.BranchName
		//转换后的分支名
		varsMap["branch"] = sanitizedBranchName
		varsMap["app_project"] = repo.Namespace
		varsMap["app_env"] = branch.AppEnv
		varsMap["on_events"] = strings.Join(branch.OnEvent, ",")
		varsMap["image_url"] = registryHost + "/" + branchNS + "/" + repo.Name
		varsMap["on_events"] = strings.Join(branch.OnEvent, ",")

		// 在当前分支设置docker/Dockerfile
		exist, _ = provider.CheckFileExist(ctx, event, branch.BranchName, DockerfilePath)
		if !exist {
			dockerfile, ok := templateCM.Data["Dockerfile"]
			if !ok {
				return errors.New(fmt.Sprintf("failed to get Dockerfile in configmap %s.", templateCM.Name))
			}
			err = provider.CreateFile(ctx, event, branch.BranchName, DockerfilePath, templates.ReplaceFileVariables(dockerfile, varsMap))
			if err != nil {
				return err
			}
		}

		// 在当前分支设置kubernetes/deploy.yaml
		deployFilePath := fmt.Sprintf("kubernetes/deploy-%s.yaml", branch.AppEnv+"-"+sanitizedBranchName)
		varsMap["deploy_file_name"] = deployFilePath
		exist, _ = provider.CheckFileExist(ctx, event, branch.BranchName, deployFilePath)
		if !exist {
			dockerfile, ok := templateCM.Data["deploy.yaml"]
			if !ok {
				return errors.New(fmt.Sprintf("failed to get deploy.yaml in configmap %s.", templateCM.Name))
			}
			err = provider.CreateFile(ctx, event, branch.BranchName, deployFilePath, templates.ReplaceFileVariables(dockerfile, varsMap))
			if err != nil {
				return err
			}
		}

		// 在默认分支设置.tekton/的流水线文件
		exist, _ = provider.CheckFileExist(ctx, event, "", branch.PrName)
		if !exist {
			pipelinerunCM, err := run.Clients.Kube.CoreV1().ConfigMaps(PublicNamespace).Get(ctx, branch.PrTemplate, metav1.GetOptions{})
			if err != nil {
				logger.Errorf("failed to get configmap %s in namespace %s", branch.PrTemplate, PublicNamespace)
				return err
			}
			pipelinerunFile, ok := pipelinerunCM.Data["template"]
			if !ok {
				return errors.New(fmt.Sprintf("failed to get template in configmap %s.", pipelinerunCM.Name))
			}
			err = provider.CreateFile(ctx, event, "", branch.PrName, templates.ReplaceFileVariables(pipelinerunFile, varsMap))
			if err != nil {
				return err
			}
		}
	}

	return nil
}
